﻿/*
 * Copyright 2013 Shaun McCloud
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace Library
{
    public static class Encryption
    {
        private const string RIJENDAL_SHARED_SECRET = "@e-!uqeseb#7chemasarUc!ku3ru@?utugep?U?8u+rup2$ph-+Ra?ew2dr+fapE";
        private static byte[] RIJENDAL_SALT = Encoding.ASCII.GetBytes("n*9#vabag-9wuJEfre5ePh@PEpu48sWaxuxeweHaruhu&w6Jefemuj8=eyuyE4pU");
        private const int KEYSIZE = 256;

        /// <summary>
        /// Encrypt the given string using AES.
        /// </summary>
        /// <param name="plainText">The text to encrypt.</param>
        public static string Encrypt(string plainText)
        {
            // we cannot continue if argument is null or empty, so throw an error
            if (string.IsNullOrEmpty(plainText))
            {
                throw new ArgumentNullException("plainText");
            }

            // encrypted string to return
            string retVal = string.Empty;

            // RijndaelManaged object used to encrypt the data
            RijndaelManaged rijndaelAlgorithm = null;

            try
            {
                // generate the key from the shared secret and the salt
                Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(RIJENDAL_SHARED_SECRET, RIJENDAL_SALT);

                // create a RijndaelManaged object w/ the specified key & IV w/ a key size of 256
                rijndaelAlgorithm = new RijndaelManaged();
                rijndaelAlgorithm.KeySize = KEYSIZE;
                rijndaelAlgorithm.Key = key.GetBytes(rijndaelAlgorithm.KeySize / 8);

                // create the engine to perform the transformation
                ICryptoTransform encryptor = rijndaelAlgorithm.CreateEncryptor(rijndaelAlgorithm.Key, rijndaelAlgorithm.IV);

                // create the streams used for encryption
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    // prepend the IV
                    msEncrypt.Write(BitConverter.GetBytes(rijndaelAlgorithm.IV.Length), 0, sizeof(int));
                    msEncrypt.Write(rijndaelAlgorithm.IV, 0, rijndaelAlgorithm.IV.Length);
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {
                            // write all data to the stream
                            swEncrypt.Write(plainText);
                            plainText = string.Empty;
                        }
                    }
                    retVal = Convert.ToBase64String(msEncrypt.ToArray());
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Cannot encrypt, error message is\n" + ex.Message);
            }
            finally
            {
                // clear out the RijndaelManaged object
                if (rijndaelAlgorithm != null)
                {
                    rijndaelAlgorithm.Clear();
                }
            }

            // finally return the encrypted string
            return retVal;
        }

        /// <summary>
        /// Decrypt the given string.
        /// </summary>
        /// <param name="cipherText">The text to decrypt.</param>
        public static string Decrypt(string cipherText)
        {
            // we cannot continue if either argument is null or empty, so throw an error
            if (string.IsNullOrEmpty(cipherText))
            {
                throw new ArgumentNullException("cipherText");
            }

            // decrypted string to return
            string retVal = string.Empty;

            // RijndaelManaged object used to decrypt the data
            RijndaelManaged rijndaelAlgorithm = null;

            try
            {
                // generate the key from the shared secret & salt
                Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(RIJENDAL_SHARED_SECRET, RIJENDAL_SALT);

                // create the streams used for decryption
                byte[] bytes = Convert.FromBase64String(cipherText);
                using (MemoryStream msDecrypt = new MemoryStream(bytes))
                {
                    // create a RijndaelManaged object w/ the specified key & IV w/ a key size of 256
                    rijndaelAlgorithm = new RijndaelManaged();
                    rijndaelAlgorithm.KeySize = KEYSIZE;
                    rijndaelAlgorithm.Key = key.GetBytes(rijndaelAlgorithm.KeySize / 8);

                    // get the initialization vector from the encrypted stream
                    rijndaelAlgorithm.IV = ReadByteArray(msDecrypt);

                    // create a decryptor to perform the stream transform
                    ICryptoTransform decryptor = rijndaelAlgorithm.CreateDecryptor(rijndaelAlgorithm.Key, rijndaelAlgorithm.IV);
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {
                            // read the decrypted bytes from the decrypting stream and place them in a string array
                            retVal = srDecrypt.ReadToEnd();
                        }
                    }
                }

            }
            catch (Exception ex)
            {
                throw new Exception("Cannot decrypt, error message is\n" + ex.Message);
            }
            finally
            {
                // clear out the RijndaelManaged object
                if (rijndaelAlgorithm != null)
                {
                    rijndaelAlgorithm.Clear();
                }
            }
            return retVal;
        }

        /// <summary>
        /// Used to return a byte array for decryption
        /// </summary>
        /// <param name="s">Stream to get the byte array from</param>
        /// <returns>the stream in byte[] format</returns>
        private static byte[] ReadByteArray(Stream s)
        {
            byte[] rawLength = new byte[sizeof(int)];
            if (s.Read(rawLength, 0, rawLength.Length) != rawLength.Length)
            {
                throw new SystemException("Stream did not contain properly formatted byte array");
            }

            byte[] buffer = new byte[BitConverter.ToInt32(rawLength, 0)];
            if (s.Read(buffer, 0, buffer.Length) != buffer.Length)
            {
                throw new SystemException("Did not read byte array properly");
            }
            return buffer;
        }

        public static string CalculateMD5Hash(string input)
        {
            // step 1, calculate MD5 hash from input
            MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
            byte[] hash = md5.ComputeHash(inputBytes);

            // step 2, convert byte array to hex string
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("x2"));
            }
            return sb.ToString();
        }
    }
}
